<template>
	<view :id="elId" :class="[is('select', props.selectable)+elId]">
		<slot v-if="!nodes[0]" />
		<!-- #ifndef APP-NVUE -->
		<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu]" />
		<!-- #endif -->
		<!-- #ifdef APP-NVUE -->
		<web-view ref="webRef" src="/static/app-plus/mp-html/local.html"
			:style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
		<!-- #endif -->
	</view>
</template>

<script lang="ts">
	import { defineComponent } from 'vue'
	import mpMixin from "../../libs/mixin/mpMixin";
	/**
	 * mp-html v2.0.4
	 * @description 富文本组件
	 * @tutorial https://github.com/jin-yufeng/mp-html
	 * @property {String}			bgColor		背景颜色，只适用与APP-NVUE
	 * @property {String}			content		用于渲染的富文本字符串（默认 true ）
	 * @property {Boolean}			copyLink	是否允许外部链接被点击时自动复制
	 * @property {String}			domain		主域名，用于拼接链接
	 * @property {String}			errorImg	图片出错时的占位图链接
	 * @property {Boolean}			lazyLoad	是否开启图片懒加载（默认 true ）
	 * @property {string}			loadingImg	图片加载过程中的占位图链接
	 * @property {Boolean}			pauseVideo	是否在播放一个视频时自动暂停其它视频（默认 true ）
	 * @property {Boolean}			previewImg	是否允许图片被点击时自动预览（默认 true ）
	 * @property {Boolean}			scrollTable	是否给每个表格添加一个滚动层使其能单独横向滚动
	 * @property {Boolean}			selectable	是否开启长按复制
	 * @property {Boolean}			setTitle	是否将 title 标签的内容设置到页面标题（默认 true ）
	 * @property {Boolean}			showImgMenu	是否允许图片被长按时显示菜单（默认 true ）
	 * @property {Object}			tagStyle	标签的默认样式
	 * @property {Boolean | Number}	useAnchor	是否使用锚点链接
	 * 
	 * @event {Function}	load	dom 结构加载完毕时触发
	 * @event {Function}	ready	所有图片加载完毕时触发
	 * @event {Function}	imgTap	图片被点击时触发
	 * @event {Function}	linkTap	链接被点击时触发
	 * @event {Function}	error	媒体加载出错时触发
	 */
	export default defineComponent({
		name: "e-parse",
		mixins: [mpMixin],
	});
</script>
<script setup lang="ts">
	import {
		ref,
		watch,
		toRefs,
		nextTick,
		onBeforeUnmount,
		getCurrentInstance
	} from 'vue'
	import {
		parseProps,
		parseEmits
	} from "./props";
	import {
		useNamespace,
		useDefaultProps
	} from '../../libs/hooks'
	import componentsProps, {
		type DefaultProps
	} from "../../libs/config/componentsProps";
	import {
		parser
	} from './parser'
	import type {ParserOptions,NodeItem} from './parser'
	// #ifndef APP-NVUE
	import node from './node/node.vue'
	// #endif
	import {
		nanoid
	} from "../../libs/utils";

	const {
		parse: parseDefaultProps
	} = toRefs(componentsProps);
	const _props = defineProps(parseProps)
	const props = useDefaultProps < DefaultProps['parse'] > (_props, parseDefaultProps);
	const emit = defineEmits(parseEmits);
	
	const {
		is
	} = useNamespace('parse')
	
	const ctx = getCurrentInstance()

	const plugins:Record<string,Function>[] = []
	let imgList:ParserOptions['imgList'] = []
	
	const elId = nanoid(21,true)

	// #ifdef APP-NVUE
	const dom = weex.requireModule('dom')
	const height = ref(3)
	// #endif

	const nodes = ref<NodeItem[]>([])

	let _in: {
		page: Record<string,any>,
		selector: string,
		scrollTop: number
	}


	/**
	 * @description 将锚点跳转的范围限定在一个 scroll-view 内
	 * @param {Object} page scroll-view 所在页面的示例
	 * @param {String} selector scroll-view 的选择器
	 * @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
	 */
	const myIn = (page: Record<string,any>, selector: string, scrollTop: number) => {
		// #ifndef APP-NVUE
		if (page && selector && scrollTop)
			_in = {
				page,
				selector,
				scrollTop
			}
		// #endif
	}

	const webRef = ref()

	let _navigateTo: {
		resolve: Function
		reject: Function
		offset: number
	}

	/**
	 * @description 锚点跳转
	 * @param {String} id 要跳转的锚点 id
	 * @param {Number} offset 跳转位置的偏移量
	 */

	const navigateTo = (id: string, offset: number) => {
		return new Promise((resolve, reject) => {
			if (!props.value.useAnchor)
				return reject('Anchor is disabled')
			offset = offset || parseInt(`${props.value.useAnchor||0}`) || 0
			// #ifdef APP-NVUE
			if (!id) {
				dom.scrollToElement(webRef.value, {
					offset
				})
				resolve(true)
			} else {
				_navigateTo = {
					resolve,
					reject,
					offset
				}
				webRef.value.evalJs(
					'uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id +
					')||{}).offsetTop}})')
			}
			// #endif
			// #ifndef APP-NVUE
			let deep = ' '
			// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
			deep = '>>>'
			// #endif
			const selector = uni.createSelectorQuery()
				// #ifndef MP-ALIPAY
				.in(_in ? _in.page : this)
				// #endif
				.select((_in ? _in.selector : `.${elId}`) + (id ? `${deep}#${id}` : ''))
				.boundingClientRect()
			if (_in)
				selector.select(_in.selector).scrollOffset(() => {})
				.select(_in.selector).boundingClientRect() // 获取 scroll-view 的位置和滚动距离
			else
				selector.selectViewport().scrollOffset(() => {}) // 获取窗口的滚动距离
			selector.exec(res => {
				if (!res[0])
					return reject('Label not found')
				const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) +
					offset
				if (_in)
					// scroll-view 跳转
					_in.page[_in.scrollTop] = scrollTop
				else
					// 页面跳转
					uni.pageScrollTo({
						scrollTop,
						duration: 300
					})
				resolve(true)
			})
			// #endif
		})
	}

	// 获取文本内容
	const getText = (_nodes:NodeItem[]) => {
      let text = '';
      (function traversal (__nodes) {
        for (let i = 0; i < __nodes.length; i++) {
          const node = __nodes[i]
          if (node.type === 'text') {
            text += node.text!.replace(/&amp;/g, '&')
          } else if (node.name === 'br') {
            text += '\n'
          } else {
            // 块级标签前后加换行
            const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
            if (isBlock && text && text[text.length - 1] !== '\n') {
              text += '\n'
            }
            // 递归获取子节点的文本
            if (node.children) {
              traversal(node.children)
            }
            if (isBlock && text[text.length - 1] !== '\n') {
              text += '\n'
            } else if (node.name === 'td' || node.name === 'th') {
              text += '\t'
            }
          }
        }
      })(_nodes || nodes.value)
      return text
    }

	// 获取内容大小和位置
	const getRect = (): Promise < {
		width: number;height: number,
		top: number,
		left: number,
		right: number
	} > => new Promise((resolve, reject) => {
		uni.createSelectorQuery()
			// #ifndef MP-ALIPAY
			.in(ctx)
			// #endif
			.select(`#${elId}`).boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(
				'Root label not found'))
	})
	
	
	let _ready:boolean
	let _videos:any[] = []
	
	/**
	 * @description 暂停播放媒体
	 */
	// const pauseMedia = () => {
	//   for (let i = (_videos || []).length; i--;) {
	//     _videos[i].pause()
	//   }
	//   // #ifdef APP-PLUS
	//   const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
	//   // #ifndef APP-NVUE
	//   let page = ctx!.parent!
	//   while (!page.$scope) page = page.parent!
	//   page.$scope.$getAppWebview().evalJS(command)
	//   // #endif
	//   // #ifdef APP-NVUE
	//   webRef.value.evalJs(command)
	//   // #endif
	//   // #endif
	// }
	
	// 推断Promise函数的返回值
	type PickPromise<P> = P extends Promise<infer R> ? R : P
	/**
	 * @description 设置内容
	 * @param {String} content html 内容
	 * @param {Boolean} append 是否在尾部追加
	 */
	const setContent = (content?: string, append ? : boolean) => {
      if (!append || !imgList) {
        imgList = []
      }
      const _nodes = new parser({...props.value,imgList,plugins}).parse(content)
      // #ifdef APP-NVUE
      if (_ready) {
        _set(nodes.value, append)
      }
      // #endif
	  nodes.value = append ? (nodes.value || []).concat(_nodes) : _nodes
      

      // #ifndef APP-NVUE
      _videos = []
      nextTick(() => {
        _hook('onLoad')
        emit('load')
      })

      if (props.value.lazyLoad || imgList!._unloadimgs! < imgList.length / 2) {
        // 设置懒加载，每 350ms 获取高度，不变则认为加载完毕
        let _height = 0
        const callback = (rect:Partial<PickPromise<ReturnType<typeof getRect>>>) => {
          if (!rect || !rect.height) rect = {}
          // 350ms 总高度无变化就触发 ready 事件
          if (rect.height === _height) {
            emit('ready', rect)
          } else {
            _height = rect.height!
            setTimeout(() => {
              getRect().then(callback).catch(callback)
            }, 350)
          }
        }
        getRect().then(callback).catch(callback)
      } else {
        // 未设置懒加载，等待所有图片加载完毕
        if (!imgList!._unloadimgs) {
          getRect().then(rect => {
            emit('ready', rect)
          }).catch(() => {
            emit('ready', {})
          })
        }
      }
      // #endif
    }

	/**
	 * @description 调用插件钩子函数
	 */
	const _hook = (name: string) => {
		for (let i = plugins.length; i--;)
			if (plugins[i][name])
				plugins[i][name]()
	}

	// #ifdef APP-NVUE
	/**
	 * @description 设置内容
	 */
	const _set = (_nodes:NodeItem[], append?: string|boolean) => {
		webRef.value.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([props.value.containerStyle?.replace(/(?:margin|padding)[^;]+/g, ''), props.value.errorImg, props.value.loadingImg, props.value.pauseVideo, props.value.scrollTable, props.value.selectable]) + ',' + append + ')')
	}

	/**
	 * @description 接收到 web-view 消息
	 */
	const _onMessage = (e: any) => {
		const message = e.detail.data[0]
		switch (message.action) {
			// web-view 初始化完毕
			case 'onJSBridgeReady':
				_ready = true
				if (nodes.value)
					_set(nodes.value)
				break
				// 内容 dom 加载完毕
			case 'onLoad':
				height.value = message.height
				_hook('onLoad')
				emit('load')
				break
				// 所有图片加载完毕
			case 'onReady':
				getRect().then(res => {
					emit('ready', res)
				}).catch(() => {})
				break
				// 总高度发生变化
			case 'onHeightChange':
				height.value = message.height
				break
				// 图片点击
			case 'onImgTap':
				emit('imgTap', message.attrs)
				if (props.value.previewImg)
					uni.previewImage({
						current: parseInt(message.attrs.i),
						urls: imgList||[]
					})
				break
				// 链接点击
			case 'onLinkTap':
				const href = message.attrs.href
				emit('linkTap', message.attrs)
				if (href) {
					// 锚点跳转
					if (href[0] == '#') {
						if (props.value.useAnchor)
							dom.scrollToElement(webRef.value, {
								offset: message.offset
							})
					}
					// 打开外链
					else if (href.includes('://')) {
						if (props.value.copyLink)
							plus.runtime.openWeb(href)
					} else
						uni.navigateTo({
							url: href,
							fail() {
								uni.switchTab({
									url: href
								})
							}
						})
				}
				break
				// 获取到锚点的偏移量
			case 'getOffset':
				if (typeof message.offset == 'number') {
					dom.scrollToElement(webRef.value, {
						offset: message.offset + _navigateTo.offset
					})
					_navigateTo.resolve()
				} else
					_navigateTo.reject('Label not found')
				break
				// 点击
			case 'onClick':
				emit('tap')
				break
				// 出错
			case 'onError':
				emit('error', {
					source: message.source,
					attrs: message.attrs
				})
		}
	}
	// #endif


	watch(() => props.value.content, (v) => {
		setContent(v)
	})


	if (props.value.content && !nodes.value.length) {
		setContent(props.value.content)
	}



	onBeforeUnmount(() => {
		_hook('onDetached')
		
	})
	
	defineExpose({
		getRect
	})

	// export default {

	// 	created() {
	// 		this.plugins = []
	// 		for (let i = plugins.length; i--;)
	// 			this.plugins.push(new plugins[i](this))
	// 	},
	// }
</script>

<style lang="scss" scoped>
	@import "../../libs/theme/parse.scss";
</style>